package com.snoob.springboot.common.tenxun.util;

import com.alibaba.fastjson.JSONObject;
import com.snoob.springboot.base.BaseResult;
import com.snoob.springboot.util.HttpHelperUtil;
import com.snoob.springboot.util.RequestUtil;
import com.snoob.springboot.util.StrUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @描述: 微信【公众号】工具类
 * @作者: lixing
 * @日期 2020/6/26 17:42
 */
public class WxGghUtil {
    /**
     * @描述 获取普通的AccessToken【 TODO 该方法不要直接调用，推荐使用定时器定时触发并存入redis缓存 】
     * @参数 [appid 公众号后台获取, secret 公众号后台获取]
     *
     * 网页授权的access_token和普通access_token区别
     *  有效期：两者有效时间都是7200s
     *  使用范围：通过网页授权获得的access_token只能获取到对应的微信用户信息，与微信用户是一对一关系；而普通的access_token在有效期内可以使用可以获取所有用户信息
     *  次数限制：普通access_token每天获取最多次数为2000次，而网页授权的access_token获取次数没有限制
     *
     * access_token是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效
     * 1、建议公众号开发者使用中控服务器统一获取和刷新access_token，其他业务逻辑服务器所使用的access_token均来自于该中控服务器，不应该各自去刷新，否则容易造成冲突，导致access_token覆盖而影响业务；
     * 2、目前access_token的有效期通过返回的expire_in来传达，目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中，中控服务器可对外继续输出的老access_token，此时公众平台后台会保证在5分钟内，新老access_token都可用，这保证了第三方业务的平滑过渡；
     * 3、access_token的有效时间可能会在未来有调整，所以中控服务器不仅需要内部定时主动刷新，还需要提供被动刷新access_token的接口，这样便于业务服务器在API调用获知access_token已超时的情况下，可以触发access_token的刷新流程
     * 公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得（需要已经成为开发者，且帐号没有异常状态）。**调用接口时，请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中，点击查看设置方法，否则将无法调用成功。**小程序无需配置IP白名单
     *
     * 获取Access token说明文档：https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     */
    public static BaseResult<String> getAccessToken(String appid, String secret) {
        String tokenUrl = MessageFormat.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appid, secret);
        String accessTokenResponseStr = HttpHelperUtil.sendRequestHttps(tokenUrl, "GET",null);
        // 响应参数
        // {"errcode":40125,"errmsg":"invalid appsecret, view more at http:\/\/t.cn\/RAEkdVq rid: 604773a3-5a985f18-248602c5"}
        // {"access_token":"42_gLYd0R5ADAJDI","expires_in":7200}
        JSONObject accessTokenResponseObj = JSONObject.parseObject(accessTokenResponseStr);
        if (accessTokenResponseObj.containsKey("errcode")) {
            return new BaseResult<String>().fail(accessTokenResponseObj.getString("errmsg"));
        }
        String accessToken = accessTokenResponseObj.getString("access_token");
        return new BaseResult<String>().success(accessToken);
    }

    /**
     * @描述 获取JSAPI_TICKET 【 TODO 该方法不要直接调用，推荐使用定时器定时触发并存入redis缓存 】
     * @参数 [accessToken]
     *
     * 微信JS-SDK是微信公众平台 面向网页开发者提供的基于微信内的网页开发工具包。
     * 通过使用微信JS-SDK，网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力，同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力
     *
     * JS-SDK说明文档：https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
     */
    public static BaseResult<String> getJsapiTicket(String accessToken) {
        String jsapiTicketUrl = MessageFormat.format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken);
        String jsapiTicketResponseStr = HttpHelperUtil.sendRequestHttps(jsapiTicketUrl, "GET",null);
        //
        JSONObject jsapiTicketResponseObj = JSONObject.parseObject(jsapiTicketResponseStr);
        if (jsapiTicketResponseObj.containsKey("errcode") && !"0".equals(jsapiTicketResponseObj.getString("errcode"))) {
            return new BaseResult<String>().fail(jsapiTicketResponseObj.getString("errmsg"));
        }
        String jsapiTicket = jsapiTicketResponseObj.getString("ticket");
        return new BaseResult<String>().success(jsapiTicket);
    }

    /**
     * @描述 JS-SDK使用权限签名算法，详细请参考：微信网页开发-JS-SDK说明文档
     * @参数 [jsapiTicket jsapiTicket, url 当前网页的URL，不包含#及其后面部分]
     * @return java.util.Map<java.lang.String,java.lang.String>
     */
    public static Map<String, String> getJsapiTicketSign(String jsapiTicket, String url) {
        Map<String, String> ret = new HashMap<String, String>(5);
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();
        String string1;
        String signature = "";
        //注意这里参数名必须全部小写，且必须有序
        string1 = MessageFormat.format("jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}",jsapiTicket,nonceStr,timestamp,url);
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes(StandardCharsets.UTF_8));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        ret.put("jsapi_ticket", jsapiTicket);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", timestamp);
        ret.put("url", url);
        ret.put("signature", signature);
        return ret;
    }
    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }
    private static String createNonceStr() {
        return UUID.randomUUID().toString();
    }
    private static String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
    // JS-SDK使用权限签名算法，详细请参考：微信网页开发-JS-SDK说明文档 end

    /**
     * @描述 TODO 判断当前用户的请求是否登录授权过，如果没有则引导用户发起登录授权操作，成功后带上code重定向到当前请求
     * @参数 [appId, request, response]
     */
    public static String getCodeByAuth(String appId, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String code = request.getParameter("code");
        if (!StrUtil.isEffective(code)) {
            // 当前code无效，需发起用户授权来获取code临时凭证
            String currentReqUrl = RequestUtil.getRequestUrl(request);
            // 以 snsapi_base 为scope发起的网页授权，是用来获取进入页面的用户的openid的，并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页（往往是业务页面）
            // 以 snsapi_userinfo 为scope发起的网页授权，是用来获取用户的基本信息的。但这种授权需要用户手动同意，并且由于用户同意过，所以无须关注，就可在授权后获取该用户的基本信息
            // 授权成功后，页面将跳转至 redirect_uri/?code=CODE&state=STATE
            String getCodeUrl = MessageFormat.format(
                    "https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type=code&scope={2}&state=STATE#wechat_redirect",
                    appId,
                    currentReqUrl,
                    "snsapi_base");
            response.sendRedirect(getCodeUrl);
            return null;
        }
        return code;
    }

    /**
     * @描述 TODO 根据 appid、secret、code 获取网页授权 access_token 和 openId
     * @参数 [appid, secret, code 微信用户授权（弹窗、静默）获取的临时凭证，作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期]
     * @return com.alibaba.fastjson.JSONObject [access_token 和 openid]
     * TODO 通过网页授权获得的access_token，只能获取到对应的微信用户信息，与微信用户是一对一关系,获取次数没有限制
     */
    public static BaseResult<JSONObject> getAccessTokenForAuthByCode(String appid, String secret, String code) {
        String getAccessTokenUrl = MessageFormat.format(
                "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code",
                appid, secret, code);
        String resString = HttpHelperUtil.sendRequestHttps(getAccessTokenUrl, "POST", null);
        JSONObject resJson = JSONObject.parseObject(resString);
        //
        if (resJson.containsKey("errcode")) {
            return new BaseResult<JSONObject>().fail(resJson.getString("error"));
        }
        return new BaseResult<JSONObject>().success(resJson);
    }

    /**
     * @描述 TODO 根据 accessToken 和 openId 获取网页授权的用户信息
     * @参数 [accessToken、openId]
     * @return com.alibaba.fastjson.JSONObject [openid、nickName、cover等等]
     */
    public static BaseResult<JSONObject> getUserInfoForAuthByAccessTokenAndOpenId(String accessToken, String openId) {
        String getUserInfoUrl = MessageFormat.format("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN", accessToken, openId);
        String resString = HttpHelperUtil.sendRequestHttps(getUserInfoUrl, "POST", null);
        JSONObject resJson = JSONObject.parseObject(resString);
        //
        if (resJson.containsKey("errcode")) {
            String error = resJson.getString("error");
            return new BaseResult<JSONObject>().fail(error);
        }
        return new BaseResult<JSONObject>().success(resJson);
    }

}